<?
/**
 * @package timeline-history
 */
(basename($_SERVER['PHP_SELF']) == basename(__FILE__)) && die('Access Denied');

/**
 * generic webapp html class
 */
class appHtml {

/**
 * Action query strings
 */
var $actionQsAdd  = false;
var $actionQsDel  = false;
var $actionQsEdit = false;
var $actionQsList = false;
var $actionQsView = false;

/**
 * Action requested by user.
 * @see getAction(), parseRequestVariables()
 */
private $actionRequest = null;

/**
 * Breadcrumb variables
 * @see addCrumb(), deleteCrumb(), getCrumb(), setCrumb()
 * @see setBreadCrumbs(), getBreadCrumbs()
 */
private $breadCrumbs = array();

/**
 * html document character set
 * @see getCharset(), setCharset(), __construct()
 */
private $charset = null;

/**
 * Enable viewing context menu via right clicks
 * @see setContextMenu()
 */
public $contextMenu = false;

/**
 * CSS uri's
 * @see addCss(), getCss(), __construct()
 */
private $css = array();

/**
 * Debugging
 * @see setDebug()
 */
public $debug = false;

/**
 * Template Directory
 * @see getTemplateDir(), setTemplateDir()
 */
private $dirTmp = null;

/**
 * html document type
 * @see getDoctype(), setDoctype(), __construct()
 */
private $doctype = null;

/**
 * Hold error messages
 * @see getErrors(), addError()
 */
private $errors = array();

/**
 * Placeholders for hit counts
 */
public $hitsCurrent = 0;
public $hitsMax     = 0;
public $hitsTotal   = 0;

/**
 * Javascript uri's
 * @see addJs(), getJs(), __construct()
 */
private $js  = array();

/**
 * html document meta headers
 * @see addMeta(), getMeta(), __construct()
 */
private $meta = array();

/**
 * Place holders for page numbers
 */
public $pageCurrent  = 1;
public $pageLast     = 1;
public $pageNext     = 1;
public $pagePrevious = 1;

/**
 * When adding objects, what property names are needed?
 * @see setPropertiesAdd(), addPropertyAdd(), getPropertiesAdd()
 */
private $propertiesAdd = array();

/**
 * When deleting objects, what property names are needed?
 * @see setPropertiesDelete(), addPropertyDelete()
 * @see getPropertiesDelete()
 */
private $propertiesDelete = array();

/**
 * When editing objects, what property names are needed?
 * @see setPropertiesEdit(), addPropertyEdit(), getPropertiesEdit()
 */
private $propertiesEdit = array();

/**
 * When listing objects, what property names are needed?
 * @see setPropertiesList(), addPropertyList(), getPropertiesList()
 */
private $propertiesList = array();

/**
 * When viewing objects, what property names are needed?
 * @see setPropertiesView(), addPropertyView(), getPropertiesView()
 */
private $propertiesView = array();

/**
 * Action request query string
 * @see __construct()
 */
protected $qsAction = null;

/**
 * Contains searchable form property names.
 * @see addSearchProperty(), getSearchProperties()
 */
private $searchProperties = array();

/**
 * Contains default search property name
 * @see setSearchPropertyDefault(), getSearchPropertyDefault()
 */
private $searchPropertyDefault = false;

/**
 * Contains default search order property name
 * @see getSearchOrderDefault(), setSearchOrderDefault()
 */
private $searchOrderDefault;

/**
 * Contains search results
 */
public $searchResults = false;

/**
 * Search variables, populated from breadcrumbs by setBreadCrumbs
 */
public  $searches     = 0;
private $searchFields = array();
private $searchTerms  = array();
private $searchTypes  = array();

/**
 * html Template Variables
 * @see getVar(), setVar(), varIsSet()
 */
private $templateVars = array();

/**
 * html document page title
 * @see getTitle(), setTitle(), __construct()
 */
private $title = null;

/**
 * root uri
 * @see getUri(), setUri()
 */
private $uri = '/';

/**
 * Constructor
 * @param $breadCrumbVars optional array of breadcrumb variable names
 */
public function __construct() {
	// query strings
	if (defined('WA_QS_ACTION')) {
		$this->qsAction = WA_QS_ACTION;
		}
	$this->parseRequestVariables();
	$this->addCrumb(WA_QS_ACTION);
	$this->addCrumb(WA_ACTION_PREVIOUS);
	$this->addCrumb(WA_QS_ID);
	$this->addCrumb(WA_QS_APPLET);
	$this->addCrumb(WA_QS_ORDER);
	$this->addCrumb(WA_QS_PAGE);
	$this->addCrumb(WA_QS_SORT);
	$this->addCrumb(WA_QS_SEARCH_FIELDS);
	$this->addCrumb(WA_QS_SEARCH_TERMS);
	$this->addCrumb(WA_QS_SEARCH_TYPES);
	$this->addCrumb(WA_QS_UID);
	$this->setBreadCrumbs();
	// Glean searches
	$fields = array(); $types = array(); $terms = array();
	$ar1 =& $this->getCrumb(WA_QS_SEARCH_TERMS);
	$ar2 =& $this->getCrumb(WA_QS_SEARCH_FIELDS);
	$ar3 =& $this->getCrumb(WA_QS_SEARCH_TYPES);
	if (is_array($ar1) and is_array($ar2) and is_array($ar3)) {
		foreach($ar1 as $id => &$data) {
			if (!isset($ar2[$id])) { continue; }
			if (!isset($ar3[$id])) { continue; }
			if (!is_string($data)) { continue; }
			if (!strlen($data) > 0) { continue; }
			if (!is_string($ar2[$id])) { continue; }
			if (!is_string($ar3[$id])) { continue; }
			if (strlen($ar2[$id]) < 1) { continue; }
			if (strlen($ar3[$id]) < 1) { continue; }
			$terms[$id] = $data;
			$fields[$id] = $ar2[$id];
			$types[$id] = $ar3[$id];
			}
		}
	if (count($terms)) {
		$this->searchFields = $fields;
		$this->searchTerms  = $terms;
		$this->searchTypes  = $types;
		$this->searches     = count($terms);
		}
	}

/**
 * Add a variable name to the breadcrumb trail.
 * @param string $varname
 * @return bool
 * @see crumbExists(), deleteCrumb(), getBreadCrumbs()
 * @todo see about escaping crumbs with htmlspecialchars() or whatever
 */
public function addCrumb($crumb=null) {
	if (!$this->isValidVarName($crumb)) { return false; }
	if (!is_array($this->breadCrumbs)) { return false; }
	if ($this->crumbExists($crumb)) { return true; }
	$this->breadCrumbs[$crumb] = null;
	return true;
	}

/**
 * Add url for css includes
 * @param string $uri
 * @return bool
 * @see getCss(), resetCss(), __construct()
 */
public function addCss($uri=null) {
	if (!is_array($this->css)) { return false; }
	if (!is_string($uri) or strlen($uri) < 1) { return false; }
	if (in_array($uri,$this->css)) { return true; }
	$this->css[] = $uri;
	return true;
	}

/**
 * Add error message to errors array
 * @param string txt
 * @return void
 * @see getErrors()
 */
public function addError($txt=null) {
	if (is_string($txt)) { $this->errors[] = $txt; }
	}

/**
 * Add url to array of urls for js includes
 * @param string $uri
 * @return bool
 * @see getJs(), __construct()
 */
public function addJs($uri=null) {
	if (!is_array($this->js)) { return false; }
	if (!is_string($uri) or strlen($uri) < 1) { return false; }
	if (in_array($uri,$this->js)) { return true; }
	$this->js[] = $uri;
	return true;
	}

/**
 * Add meta for header includes
 * @param string $txt
 * @return bool
 * @see getMeta(), __construct()
 */
public function addMeta($txt=null) {
	if (!is_array($this->meta)) { return false; }
	if (!is_string($txt) or strlen($txt) < 1) { return false; }
	if (in_array($txt,$this->meta)) { return true; }
	$this->meta[] = $txt;
	}

/**
 * Add an individual property name visible to html forms when adding objects.
 * @see setPropertiesAdd(), getPropertiesAdd().
 */
public function addPropertyAdd($prop) {
	if (!is_array($this->propertiesAdd)) { return false; }
	$this->propertiesAdd[] = $prop;
	return true;
	}

/**
 * Add an individual property name visible to html forms when deleting objects.
 * @see setPropertiesDelete(), getPropertiesDelete().
 */
public function addPropertyDelete($prop) {
	if (!is_array($this->propertiesDelete)) { return false; }
	$this->propertiesDelete[] = $prop;
	return true;
	}

/**
 * Add an individual property name visible to html forms when editing objects.
 * @see setPropertiesEdit(), getPropertiesEdit().
 */
public function addPropertyEdit($prop) {
	if (!is_array($this->propertiesEdit)) { return false; }
	$this->propertiesEdit[] = $prop;
	return true;
	}

/**
 * Add an individual property name visible to html forms when listing objects.
 * @see setPropertiesList(), getPropertiesList().
 */
public function addPropertyList($prop) {
	if (!is_array($this->propertiesList)) { return false; }
	$this->propertiesList[] = $prop;
	return true;
	}

/**
 * Add an individual property name visible to html forms when viewing objects.
 * @see setPropertiesView(), getPropertiesView().
 */
public function addPropertyView($prop) {
	if (!is_array($this->propertiesView)) { return false; }
	$this->propertiesView[] = $prop;
	return true;
	}

/**
 * Used to define searchable property names for html forms
 */
public function addSearchProperty($prop) {
	if (!is_array($this->searchProperties)) { return false; }
	if (in_array($prop,$this->searchProperties)) { return true; }
	$this->searchProperties[] = $prop;
	return true;
	}

/**
 * Does a breadcrumb variable name exist?
 * @param string $crumb
 * @return bool
 */
public function crumbExists($crumb=null) {
	if (!is_array($this->breadCrumbs)) { return false; }
	return array_key_exists($crumb,$this->breadCrumbs);
	}

/**
 * Remove a variable from the breadcrumb trail and reconstruct it
 * @param string $crumb
 * @return bool
 * @see addCrumb()
 */
public function deleteCrumb($crumb=null) {
	if (!is_string($crumb)) { return false; }
	if (!is_array($this->breadCrumbs)) { return false; }
	if (!$this->crumbExists($crumb)) { return true; }
	$c = array();
	foreach($this->breadCrumbs as $key => $data) {
		if ($key == $crumb) { continue; }
		$c[$key] = $data;
		}
	$this->breadCrumbs = $c;
	return true;
	}

/**
 * Populate a phpdbo object when adding it from a posted form.
 * @param $dbo - phpdbo object
 * @return bool
 */
public function doQueryAdd(&$dbo=null) {
	if (!($dbo instanceof phpdbo)) { return false; }
	$dbo->resetProperties();
	$props = $this->getPropertiesAdd();
	if (is_array($props) and count($props) > 0) {
		foreach($props as $prop) {
			if (array_key_exists($prop,$_REQUEST)) {
				if (is_string($_REQUEST[$prop])) {
					$dbo->setProperty($prop,trim($_REQUEST[$prop]));
					} else {
					$dbo->setProperty($prop,$_REQUEST[$prop]);
					}
				} else {
				$dbo->setProperty($prop,'');
				}
			}
		}
	return true;
	}

/**
 * Populate a phpdbo oject when editing it from a posted form.
 * @param $dbo - phpdbo object
 * @param $id  - id of phpdbo object to query
 * @return mixed
 */
public function doQueryEdit(&$dbo=null, $id=false) {
	if (!($dbo instanceof phpdbo)) { return false; }
	$dbo->resetProperties();
	if (!($dbo->populateFromDb($id) === true)) {
		return 'failed to populate form information';
		}
	// Fill in any information provided via an earlier submit
	$props = $this->getPropertiesEdit();
	if (is_array($props) and count($props) > 0) {
		foreach($props as $prop) {
			if (array_key_exists($prop,$_REQUEST)) {
				$dbo->setProperty($prop,trim($_REQUEST[$prop]));
				}
			}
		}
	return true;
	}

/**
 * Perform a list query on a phpdbo object.
 * @param $dbo - phpdbo object
 * @return mixed
 */
public function doQueryList(&$dbo=null, $debug=false) {
	if (!($dbo instanceof phpdbo)) { return false; }
	if (!($dbo->_searchEnabled === true)) {
		return 'searching has been disabled, possibly due to errors';
		}
	// sanity check on vars needed for paging
	if (!is_numeric($this->pageCurrent) or $this->pageCurrent < 1) {
		return 'must set pageCurrent before doQueryList()';
		}
	if (!is_numeric($this->hitsMax) or $this->hitsMax < 0) {
		return 'must set hitsMax before doQueryList()';
		}
	// add list properties
	$props = $this->getPropertiesList();
	if (!is_array($props) or count($props) < 1) {
		return 'list properties have not been defined';
		}
	foreach($props as $prop) {
		if (!$dbo->setSearchSelect($prop)) {
			return 'setSearchSelect(' . $prop . ') failed';
			}
		}
	// set the default order column if possible
	if (!is_array($dbo->_searchOrder) or count($dbo->_searchOrder) < 1) {
		$junk = explode(' ',$this->getSearchOrderDefault());
		$prop = $junk[0];
		if ($dbo->isProperty($prop)) {
			if (count($junk) == 2) {
				$dbo->setSearchOrder($prop,$junk[1]);
				} else {
				$dbo->setSearchOrder($prop);
				}
			}
		}
	// Get the total hits count
	$hits = $dbo->queryHitsTotal($debug);
	if (!is_numeric($hits)) {
		return 'queryHitsTotal() failed: ' . $hits;
		}
	$this->hitsTotal = $hits;
	// Not much point to going further if no hits ;)
	if ($hits < 1) {
		$this->hitsCurrent = 0;
		$this->searchResults = array();
		return true;
		}
	//
	// Apply paging and limits, unless download was requested
	//
	if ($this->hitsMax == 0 or $this->downloadRequested()) {
		$this->pageCurrent = 1;
		$this->pageNext = 1;
		$this->pagePrevious = 1;
		$this->pageLast = 1;
		$limit = $this->hitsTotal;
		$dbo->setSearchLimit($limit);
		} else {
		$pagesMax     = ceil($this->hitsTotal / $this->hitsMax);
		if ($this->pageCurrent > $pagesMax) {
			$this->pageCurrent = $pagesMax - 1;
			}
		$pageCurrent    = $this->pageCurrent;
		$this->pageNext = $this->pageCurrent + 1;
		$this->pagePrevious = $this->pageCurrent - 1;
		if ($this->pageNext > $pagesMax) {
			$this->pageNext = $pagesMax;
			}
		if ($this->pagePrevious < 1) {
			$this->pagePrevious = 0;
			}
		$this->pageLast = $pagesMax;
		if ($this->pageCurrent > 1) {
			$limit = ($this->hitsMax * ($this->pageCurrent - 1)) . ','
			. $this->hitsMax;
			} else {
			$limit = $this->hitsMax;
			}
		$dbo->setSearchLimit($limit);
		}
	// Do the query
	$rows = $dbo->query($debug);
		if (!is_array($rows)) {
		$this->hitsCurrent = 0;
		$this->hitsTotal   = 0;
		return 'query() failed: ' . $rows;
		}
	$this->hitsCurrent = count($rows);
	$this->searchResults = &$rows;
	return true;
	}

/**
 * Retrieve the sql statement behind a doQueryList() statement
 * @param $dbo - phpdbo object
 * @return mixed
 */
public function doQueryListSql(&$dbo=null, $debug=false) {
	if (!($dbo instanceof phpdbo)) { return false; }
	if (!($dbo->_searchEnabled === true)) {
		return 'searching has been disabled, possibly due to errors';
		}
	// add list properties
	$props = $this->getPropertiesList();
	if (!is_array($props) or count($props) < 1) {
		return 'list properties have not been defined';
		}
	foreach($props as $prop) {
		if (!$dbo->setSearchSelect($prop)) {
			return 'setSearchSelect(' . $prop . ') failed';
			}
		}
	// set the default order column if possible
	if (!is_array($dbo->_searchOrder) or count($dbo->_searchOrder) < 1) {
		$junk = explode(' ',$this->getSearchOrderDefault());
		$prop = $junk[0];
		if ($dbo->isProperty($prop)) {
			if (count($junk) == 2) {
				$dbo->setSearchOrder($prop,$junk[1]);
				} else {
				$dbo->setSearchOrder($prop);
				}
			}
		}
	//
	// Override normal settings
	//
	$this->pageCurrent = 1;
	$this->pageNext = 1;
	$this->pagePrevious = 1;
	$this->pageLast = 1;
	die($dbo->getListSqlStatement());
	}

/**
 * Has the user requested a download?
 * @return bool
 */
public function downloadRequested() {
	return (isset($_REQUEST[WA_QS_DOWNLOAD]));
	}

/**
 * Draw a generic page with the specified text.
 * @param string $text
 * @return string html
 */
public function drawPageMsg($text=null) {
	$h = array($this->getPageHeader());
	$h[] = $text;
	$h[] = $this->getPageFooter();
	return implode("\n",$h) . "\n";
	}

/**
 * Obtain requested form/query string action
 * @return mixed
 * @see parseRequestVariables()
 */
public function getAction() { return $this->actionRequest; }

/**
 * Get action query string with breadcrumb and optionally ID
 * If $id is 0, the qs will be returned with &id= on the end.  If
 * $id is numeric and greater than 0, qs will be returned with &id=$id.
 * Otherwise, getBreadCrumbsAction() will include the ID if it is already
 * set.
 * @param string $action
 * @param int $id - optional
 */
public function getActionQs($action=null,$id=null) {
	if (!is_string($action) or strlen($action) < 1) { return false; }
	// exclude $this->qsAction if it is already in the bread crumb
	$exclude = array($this->qsAction);
	// If $id specified, exclude it.
	if (is_numeric($id)) { $exclude[] = WA_QS_ID; }
	$tmp_ar  = array($this->qsAction . '=' . $action);
	foreach($this->getBreadCrumbs($exclude) as $crumb => $data) {
		$tmp_ar[] = $crumb . '=' . urlencode($data);
		}
	$qs = './?' . implode('&',$tmp_ar);
	// append id if specified
	if (is_numeric($id)) {
		if ($id > 0) {
			return $qs . '&' . WA_QS_ID . '=' . $id;
			} else {
			return $qs . '&' . WA_QS_ID . '=';
			}
		}
	return $qs;
	}

/**
 * Get applet request query string with action
 * @param string $applet
 * @param string $action
 * @return string
 */
public function getAppletActionQs($applet = null, $action = null) {
	return './?' . WA_QS_APPLET . '=' . $applet . '&'
	. WA_QS_ACTION . '=' . $action;
	}

/**
 * Get applet request query string
 * @param string $applet
 * @return string
 */
public function getAppletQs($applet = null) {
	$qs = './?' . WA_QS_APPLET . '=';
	return (empty($applet)) ? $qs : $qs .= $applet;
	}

/**
 * Obtain requested applet name
 * @return string $applet_request on success
 * @return bool false on failures or no action request
 */
public function getAppletRequest() {
	return $this->getCrumb(WA_QS_APPLET);
	}

/**
 * Get array of breadcrumbs with empty/optional crumbs stripped out.
 * @param array $exclude_crumbs - crumbs to optionally strip out
 * @return array crumbs
 */
public function getBreadCrumbs($exclude=null) {
	if (!is_array($this->breadCrumbs)) { return array(); }
	$c = array();
	foreach($this->breadCrumbs as $crumb => $data) {
		if (is_array($exclude) and in_array($crumb,$exclude)) { continue; }
		if ($data == null or $data == '') { continue; }
		if (is_array($data)) {
			foreach($data as $key => $txt) {
				$c[$crumb . '[' . $key . ']'] = $txt;
				}
			} else {
			$c[$crumb] = $data;
			}
		}
	return $c;
	}

/**
 * Retrieve constructed query string bread crumb trail with specified action
 * @param string $action (required)
 * @return mixed
 */
public function getBreadCrumbsAction($action=null) {
	if (!is_string($action) or strlen($action) < 1) { return false; }
	// exclude $this->qsAction if it is already in the bread crumb
	$exclude = array($this->qsAction);
	$tmp_ar  = array($this->qsAction . '=' . $action);
	foreach($this->getBreadCrumbs($exclude) as $crumb => $data) {
		$tmp_ar[] = $crumb . '=' . urlencode($data);
		}
	return './?' . implode('&',$tmp_ar);
	}

/**
 * Get document character set
 * @return mixed
 */
public function getCharset() { return $this->charset; }

/**
 * Retrieve value of specified bread crumb.
 * @param string $crumb
 * @return mixed
 */
public function getCrumb($crumb) {
	if (!$this->crumbExists($crumb)) { return false; }
	return $this->breadCrumbs[$crumb];
	}

/**
 * Get array of css uri's to include in document requests
 * @return mixed
 */
public function getCss() { return $this->css; }

/**
 * Get document type
 * @return mixed
 */
public function getDoctype() { return $this->doctype; }

/**
 * Retrieve error messages
 * @return mixed
 * @see addError()
 */
public function getErrors() { return $this->errors; }

/**
 * Return specified form action via hidden input
 * @param string $formAction
 * @return string
 */
public function getFormAction($formAction=null) {
	return '<input type="hidden" name="' . $this->qsAction . '" value="'
	. $formAction . '">' . "\n";
	}

/**
 * Generate form-based breadcrumb with HIDDEN inputs
 * @param string $action - optionally specify form action
 * @return string html
 */
public function getFormBreadCrumbs($action=null) {
	$exclude = array();
	$hidden  = array();
	if (!($action === null)) {
		$exclude[] = $this->qsAction;
		$hidden[$this->qsAction] = $action;
		}
	foreach($this->getBreadCrumbs($exclude) as $crumb => $data) {
		$hidden[$crumb] = $data;
		}
	if (count($hidden) < 1) { return "\n"; }
	$h = array();
	foreach($hidden as $crumb => $data) {
		$h[] = '<input type="hidden" name="' . $crumb . '" value="'
		. urlencode($data) . '">';
		}
	return implode("\n",$h) . "\n";
	}

/**
 * Get standard html form footer
 * @return mixed
 */
public function getFormFooter() {
	return "</form>\n";
	}

/**
 * Get standard html form header
 * @param string $form_id = optional form id
 * @param string $on_submit: optional javascript onSubmit() function to call
 * @param string $action = optional form action
 * @param string $target = optional target
 * @param bool $autocomplete
 * @return mixed
 * @see getFormFooter()
 */
public function getFormHeader($form_id=null,$on_submit=null,$action='./',
                              $target=null,$autocomplete=false) {
	$h = '<form';
	if (is_string($form_id)) {
		$h .= ' id="' . $form_id . '" name="' . $form_id . '"';
		}
	if (is_string($on_submit)) {
		$h .= ' onSubmit="' . $on_submit . '"';
		}
	if ($action === null) {
		$h .= ' action="./"';
		} else {
		$h .= ' action="' . $action . '"';
		}
	if (is_string($target)) {
		$h .= ' target="' . $target . '"';
		}
	$h .= ' method="post"';
	if (!($autocomplete === true)) {
		$h .= ' autocomplete="off"';
		}
	$h .= '>';
	return $h . "\n";
	}

/**
 * Get form to search list object
 * @param $dbo phpdbo object
 * @return string html form
 */
public function getFormListSearch(&$dbo = null) {
	if (!($dbo instanceof phpdbo)) { return "\n"; }
	// populate needed vars
	$reset_qs  = $this->getQsStripSearch();
	$search_qs = 'javascript:document.search_form.submit()';
	$reset_txt = '<img src="images/off16.png" border="0" height="16" '
	. 'width="16" alt="Reset Search">';
	$search_txt = '<img src="images/query16.png" border="0" height="16" '
	. 'width="16" alt="Search">';
	$href_ar   = array();
	$href_ar[] = '<a href="' . $search_qs . '">' . $search_txt . '</A>';
	$href_ar[] = '<a href="' . $reset_qs . '">' . $reset_txt . '</A>';
	$links = implode('',$href_ar);
	$next = 0;
	// generate html
	$h   = array();
	$h[] = '<script type="text/javascript">
function subOnEnter(formField, e) {
	var keycode;
	if (window.event) keycode = window.event.keyCode;
	else if (e) keycode = e.which;
	else return true;
	if (keycode == 13) {
		formField.form.submit();
		return false;
		} else {
		return true;
		}
	}
</script>';
	$h[] = $this->getFormHeader('search_form');
	$h[] = $this->getFormBreadCrumbs();
	// existing searches...
	for($next=0; $next < $this->searches; $next++) {
		$h[] = '<select name="' . WA_QS_SEARCH_FIELDS . '[' . $next . ']">';
		$def = $this->getSearchField($next);
		$h[] = $this->getFormSelectSearchProperty($dbo, $def);
		$h[] = '</select>';
		$h[] = '<select name="' . WA_QS_SEARCH_TYPES . '[' . $next . ']">';
		$def = $this->getSearchType($next);
		$h[] = $this->getFormSelectSearchType($def);
		$h[] = '</select>';
		$def = $this->getSearchTerm($next);
		$h[] = '<input type="text" name="' . WA_QS_SEARCH_TERMS . '[' . $next . ']" '
		     . 'length="30" maxlength="50" value="' . $def . '" '
		     . 'onKeyPress="return subOnEnter(this,event)">';
		$h[] = $links . '<BR />';
		}
	// next search in line...
	$h[] = '<select name="' . WA_QS_SEARCH_FIELDS . '[' . $next . ']">';
	$h[] = $this->getFormSelectSearchProperty($dbo);
	$h[] = '</select>';
	$h[] = '<select name="' . WA_QS_SEARCH_TYPES . '[' . $next . ']">';
	$h[] = $this->getFormSelectSearchType();
	$h[] = '</select>';
	$h[] = '<input type="text" id="inputLast" name="' . WA_QS_SEARCH_TERMS . '[' . $next . ']" '
	     . 'length="30" maxlength="50" value="" '
	     . 'onKeyPress="return subOnEnter(this,event)">';
	$h[] = $links;
	$h[] = $this->getFormFooter();
	return implode("\n", $h) . "\n";
	}

/**
 * Get search properties selection options.
 * @param $dbo object phpdbo object
 * @param $default default search property (optional)
 * @return string html select options
 */
private function getFormSelectSearchProperty(&$dbo = null, $default = false) {
	if (!($dbo instanceof phpdbo)) { return "\n"; }
	if (!$dbo->isProperty($default)) {
		$default = $this->getSearchPropertyDefault();
		}
	$props = $this->getSearchProperties();
	if (!is_array($props) or count($props) < 1) { return "\n"; }
	// sort these props by description
	$opts = array();
	foreach($props as $prop) {
		if (!$dbo->isProperty($prop)) { continue; }
		$opts[$prop] = $dbo->getPropertyDescription($prop);
		}
	asort($opts, SORT_STRING);
	$h = array();
	foreach($opts as $prop => $desc) {
		$sel = ($prop == $default) ? ' selected' : '';
		$h[] = '<option value="' . $prop . '"' . $sel . '>'
		     . $desc . '</option>';
		}
	return implode("\n",$h) . "\n";
	}

/**
 * Get search type selection options.
 * @param string default search type (optional)
 * @return string html select options
 */
private function getFormSelectSearchType($default='contains') {
	$search_types =
	array('equals','contains','begins with','ends with',
	      'less than','greater than','does not equal',
	      'does not contain','does not begin with',
	      'does not end with');
	$h = array();
	foreach($search_types as $type) {
		$sel = ($default == $type) ? ' selected' : '';
		$h[] = '<option value="' . $type . '"' . $sel . '>' . $type . '</option>';
		}
	return implode("\n",$h) . "\n";
	}

/**
 * Get page header charset string
 * @return mixed
 */
public function getHeaderCharset() {
	if (!is_string($this->charset)) { return null; }
	return '<meta http-equiv="Content-Type" content="text/html; charset='
	. $this->charset . '">' . "\n";
	}

/**
 * Get page header css includes
 * @return mixed
 */
public function getHeaderCss() {
	if (!is_array($this->css) or count($this->css) < 1) { return null; }
	$h = array();
	foreach($this->css as $txt) {
		$h[] = '<link rel="StyleSheet" href="' . $txt . '" type="text/css" '
		. 'media="screen,print">';
		}
	return implode("\n",$h) . "\n";
	}

/**
 * Get page header doctype string
 * @return mixed
 */
public function getHeaderDoctype() {
	return (is_string($this->doctype)) ? $this->doctype . "\n" : null;
	}

/**
 * Get page header javascript includes
 * @return mixed
 */
public function getHeaderJs() {
	if (!is_array($this->js) or count($this->js) < 1) { return null; }
	$h = array();
	foreach($this->js as $txt) {
		$h[] = '<script type="text/javascript" src="' . $txt . '"></script>';
		}
	return implode("\n",$h) . "\n";
	}

/**
 * Get page header meta string
 * @return mixed
 */
public function getHeaderMeta() {
	if (!is_array($this->meta) or count($this->meta) < 1) { return null; }
	return implode("\n",$this->meta) . "\n";
	}

/**
 * Get page header title
 * @return mixed
 */
public function getHeaderTitle() {
	if (!is_string($this->title)) { return null; }
	return '<title>' . htmlspecialchars($this->title) . '</title>' . "\n";
	}

/**
 * Get array of urls for js includes
 * @return mixed
 * @see addJs(), __construct()
 */
public function getJs() { return $this->js; }

/**
 * Get javascript error alert with message.
 * @param string $msg
 * @return string javascript alert message
 */
public function getJsErrorAlert($msg=null) {
	if (!is_string($msg)) { $msg = 'Unspecified Error'; }
	$h   = array('<script type="text/javascript">');
	$h[] = 'alert("' . addslashes($msg) . '");';
	$h[] = '</script>';
	return implode("\n",$h) . "\n";
	}

/**
 * Get javascript to redirect top document to specified url
 * @return string javascript code
 */
public function getJsReloadTop($uri=null) {
	if (!is_string($uri)) {
		$m = 'Cannot redirect to invalid or empty URI.';
		die($this->getJsErrorAlert($m));
		}
	$h   = array('<script type="text/javascript">');
	$h[] = 'top.location = "' . urldecode($uri) . '";';
	$h[] = '</script>';
	return implode("\n",$h) . "\n";
	}

/**
 * Get paging index that allows scrolling through pages of list results
 * Returns html row containing clickable page number links to navigate
 * through search results.
 * @return string html
 */
public function getListPager() {
	// required info
	$hitsCurrent = $this->hitsCurrent;
	$hitsMax     = $this->hitsMax;
	$hitsTotal   = $this->hitsTotal;
	$pageCurrent = $this->pageCurrent;
	$pageLast    = $this->pageLast;
	$pageNext    = $this->pageNext;
	$pagePrev    = $this->pagePrevious;
	if (!is_numeric($hitsCurrent)) { return "\n"; }
	if (!is_numeric($hitsMax))     { return "\n"; }
	if (!is_numeric($hitsTotal))   { return "\n"; }
	if (!is_numeric($pageCurrent)) { return "\n"; }
	if (!is_numeric($pageLast))    { return "\n"; }
	if (!is_numeric($pageNext))    { return "\n"; }
	if (!is_numeric($pagePrev))    { return "\n"; }
	// initialize the breadcrumb, excluding page
	$c = $this->getBreadCrumbs(array(WA_QS_PAGE));
	$crumbs = array();
	if (is_array($c) and count($c)) {
		foreach($c as $var => $val) {
			$crumbs[] = urlencode($var) . '=' . urlencode($val);
			}
		}
	// generate base query string
	$base_qs = './?' . implode('&',$crumbs) . '&' . WA_QS_PAGE . '=';
	// generate output
	$h   = array();
	// generate totals descriptive text
	if ($pageLast < 2) {
		$totals_txt = "Page 1 of 1 ($hitsTotal hits)";
		} else {
		$totals_txt = "Page $pageCurrent of $pageLast ($hitsTotal hits)&nbsp;&nbsp;";
		}
	// text containing clickable links
	$links_txt = '';
	// we have less than two pages of results
	if ($pageLast > 1) {
		// first page link
		if ($pageCurrent > 1) {
			$qs = $base_qs . '1';
			$href = '<a href="' . $qs . '">&lt;&lt;</A>';
			} else {
			$href = '<a href="javascript:void()">&lt;&lt;</a>';
			}
		$links_txt .= $href;
		// previous page link
		if ($pagePrev > 0) {
			$qs = $base_qs . $pagePrev;
			$href = '<a href="' . $qs . '">&lt;</A>';
			} else {
			$href  = '<a href="javascript:void()">&lt;</a>';
			}
		$links_txt .= $href;
		// next page link
		if ($pageCurrent < $pageLast) {
			$qs = $base_qs . $pageNext;
			$nextHref = '<a href="' . $qs . '">&gt;</A>';
			} else {
			$nextHref = '<a href="javascript:void()">&gt;</a>';
			}
		// last page link
		if ($pageCurrent < $pageLast) {
			$qs = $base_qs . $pageLast;
			$lastHref = '&nbsp;<a href="' . $qs . '">&gt;&gt;</A>';
			} else {
			$lastHref = '<a href="javascript:void()">&gt;&gt;</a>';
			}
		// don't list out hundreds of pages, max jump back or forward to 10
		$max_back = $pageCurrent - 10;
		$max_forw = $pageCurrent + 10;
		if ($max_back < 1) {
			$diff     = abs($max_back) + 1;
			$max_back = ($max_back + $diff);
			$max_forw = $max_forw + $diff;
			}
		if (($max_forw > $pageLast) and ($pageLast > 10)) {
			$diff     = $pageLast - $max_forw;
			$max_back = $max_back + $diff;
			$max_forw = $pageLast;
			}
		if ($max_forw > $pageLast) { $max_forw = $pageLast; }
		if ($max_back < 1) { $max_back = 1; }
		for ($j=$max_back; $j <= $max_forw; $j++ ) {
			$nurl = $base_qs . $j;
			$num_text = $j;
			if ($j == $pageCurrent) {
				$links_txt .= '<a class="curPagerLink" href="' . $nurl . '">' . $num_text . '</A>';
				} else {
				$links_txt .= '<a href="' . $nurl . '">' . $num_text . '</A>';
				}
			}
		// next page and last links
		$links_txt .= $nextHref . $lastHref;
		}
	$h[] = $totals_txt . $links_txt;
	return implode("\n", $h) . "\n";
	}

/**
 * Get sortable list header table row
 * Returns html table row with headers that are clickable in order to sort
 * a phpdbo object list.  Each clickable link will include the breadcrumb.
 * @param object $dbo - phpdbo object
 * @param bool $showId - show id property
 * @param array $skipProps - skip showing property names in this array
 * @return string html
 */
public function getListSortableHeader(&$dbo = null, $showId = false,
                                      $skipProps = null) {
	// sanity checks
	if (!($dbo instanceof phpdbo))        { return "\n"; }
	if (!is_array($this->propertiesList)) { return "\n"; }
	if (count($this->propertiesList) < 1) { return "\n"; }
	if (!is_array($skipProps)) { $skipProps = array(); }
	// initialize the breadcrumb, excluding sort, order, and page
	$c = $this->getBreadCrumbs(array(WA_QS_SORT,WA_QS_ORDER,WA_QS_PAGE));
	$crumbs = array();
	if (is_array($c) and count($c)) {
		foreach($c as $var => $val) {
			$crumbs[] = urlencode($var) . '=' . urlencode($val);
			}
		}
	// what is the current order/sort?
	$cur_o = $this->getCrumb(WA_QS_ORDER);
	$cur_s = $this->getCrumb(WA_QS_SORT);
	// skip the id property
	$idProp = $dbo->getIdProperty();
	// generate the html
	$h  = array('<thead>');
	$h[] = '<tr>';
	foreach($this->propertiesList as $num => $prop) {
		if ($prop == $idProp and !$showId) { continue; }
		if (in_array($prop, $skipProps))   { continue; }
		$qs = './?' . implode('&',$crumbs) . '&' . WA_QS_ORDER . '=' . $num;
		// only set extra direction on currently sorted property
		if ($num == $cur_o) {
			if ($cur_s !== 'd') {
				$qs .= '&' . WA_QS_SORT . '=d';
				}
			}
		$ln = '<a href="' . $qs . '">'
		    . $dbo->getPropertyDescription($prop) . '</a>';
		$h[] = '<th>' . $ln . '</th>';
		}
	$h[] = '</tr>';
	$h[] = '</thead>';
	return implode("\n",$h) . "\n";
	}

/**
 * Get array of meta strings for header includes
 * @return mixed
 * @see addMeta(), __construct()
 */
public function getMeta() { return $this->meta; }

/**
 * Get a unique number
 * @return int
 */
public function getNum() {
	static $num = 0; return $num++;
	}

/**
 * Generate onClick statement to load an url in the current window.
 * @param string url
 * @return string
 */
public function getOnClick($url = null) {
	return 'onClick="javascript:window.location.href=\'' . $url . '\'"';
	}

/**
 * Get error message page
 * @deprecated must remove
 * @param string error message (optional)
 * @return void
 */
public function getPageError($msg = null) {
	$msg = (empty($msg)) ? 'Unknown Error Encountered' : $msg;
	$this->setVar('msg', $msg);
	$this->setTitle('Error');
	die($this->drawPageMsg($msg));
	}

/**
 * Get page footer
 * @return string
 */
public function getPageFooter() {
	return "\n</body>\n</html>\n";
	}

/**
 * Get html page header
 * @param string $bodyArgs = optional <body> arguments
 * @param bool skipBody - if true will return everything up to </head>
 * @return html string
 * @see getFooter()
 */
public function getPageHeader($bodyArgs=null,$skipBody=false) {
	// collect pieces and parts from elsewhere...
	$doctype = $this->getHeaderDoctype();
	$charset = $this->getHeaderCharset();
	$meta    = $this->getHeaderMeta();
	$css     = $this->getHeaderCss();
	$js      = $this->getHeaderJs();
	$title   = $this->getHeaderTitle();
	// generate the html
	$h = array();
	if (is_string($doctype)) { $h[] = trim($doctype); }
	$h[] = '<html>';
	$h[] = '<head>';
	if (is_string($charset)) { $h[] = trim($charset); }
	if (is_string($meta))    { $h[] = trim($meta);    }
	if (is_string($css))     { $h[] = trim($css);     }
	if (is_string($js))      { $h[] = trim($js);      }
	if (is_string($title))   { $h[] = trim($title);   }
	if ($skipBody === true) {
		return implode("\n",$h) . "\n";
		}
	$h[] = '</head>';
	$ba = (is_string($bodyArgs)) ? ' ' . $bodyArgs : '';
	if (!($this->contextMenu === true)) {
		$ba .= ' oncontextmenu="return false;"';
		}
	$h[] = '<body' . $ba . '>';
	return implode("\n", $h) . "\n";
	}

/**
 * Retrieve uri for specified page relative to $this->uri
 * @param string pageName
 * @return string
 */
public function getPageUri($page=null) {
	return $this->uri . $page;
	}

/**
 * Retrieve list of property names available to forms when listing objects
 * @see addPropertyList(), setPropertiesList()
 */
public function getPropertiesList() {
	return $this->propertiesList;
	}

/**
 * Get query string with existing search terms stripped off
 * @return string querystring
 */
private function getQsStripSearch() {
	$x = array(WA_QS_SEARCH_FIELDS,WA_QS_SEARCH_TERMS,WA_QS_SEARCH_TYPES,
	WA_QS_PAGE,WA_QS_ORDER,WA_QS_SORT);
	$tmp_ar = array();
	foreach($this->getBreadCrumbs($x) as $crumb => $data) {
		$tmp_ar[] = $crumb . '=' . urlencode($data);
		}
	return './?' . implode('&',$tmp_ar);
	}

/**
 * Get specified request variable
 * @param string $varname
 * @param bool $postOnly
 * @return mixed request variable
 */
public function getRequestVar($varname=null,$postOnly=false) {
	if (!$this->isValidVarName($varname)) { return false; }
	if (!is_bool($postOnly)) { return false; }
	// required to be a POST variable
	if ($postOnly === true) {
		if (!isset($_POST)) { return false; }
		if (!is_array($_POST)) { return false; }
		if (!isset($_POST[$varname])) { return false; }
		if (is_array($_POST[$varname])) {
			return $_POST[$varname];
			} else {
			return trim($_POST[$varname]);
			}
		}
	if (!isset($_REQUEST)) { return false; }
	if (!is_array($_REQUEST)) { return false; }
	if (isset($_REQUEST[$varname])) {
		if (is_array($_REQUEST[$varname])) {
			return $_REQUEST[$varname];
			} else {
			return trim($_REQUEST[$varname]);
			}
		}
	return false;
	}

/**
 * Wrapper function to get search properties
 */
public function getSearchField($num=null) {
	return (isset($this->searchFields[$num])) ? $this->searchFields[$num] : '';
	}
public function getSearchType($num=null) {
	return (isset($this->searchTypes[$num])) ? $this->searchTypes[$num] : '';
	}
public function getSearchTerm($num=null) {
	return (isset($this->searchTerms[$num])) ? $this->searchTerms[$num] : '';
	}

/**
 * Retrieve the default search property order name
 */
public function getSearchOrderDefault() {
	return $this->searchOrderDefault;
	}

/**
 * Use to retrieve list of search property names
 */
public function getSearchProperties() {
	return $this->searchProperties;
	}

/**
 * Retrieve the default search property name
 */
public function getSearchPropertyDefault() {
	return $this->searchPropertyDefault;
	}

/**
 * Include specified template and die()
 * @param string $template
 * @return void
 */
public function getTemplate($template=null) {
	if (!is_file($this->dirTmp . '/' . $template)) {
		die('FATAL ERROR: template not located: ' . htmlspecialchars($template));
		}
	die(include($this->dirTmp . '/' . $template));
	}

/**
 * Get document title
 * @return mixed
 */
public function getTitle() { return $this->title;   }

/**
 * Wrapper to get uri
 */
public function getUri() { return $this->uri; }

/**
 * Get specified template variable
 * @return mixed
 * @see setVar(), varIsSet()
 */
public function &getVar($varname=null) {
	$err = false;
	if (!is_array($this->templateVars)) { return $err; }
	if (!array_key_exists($varname,$this->templateVars)) { return $err; }
	return $this->templateVars[$varname];
	}

/**
 * Has the user confirmed input?
 * @return bool
 */
public function hasConfirmed() {
	return (isset($_REQUEST[WA_QS_CONFIRM]));
	}

/**
 * htmlBullet()
 */
function htmlBullet($text='',$echo=false) {
	$string = $this->htmlSpacer(false) . '-&gt;' . $this->htmlSpacer(false)
	. $text . $this->htmlSpacer(false);
	if ($echo) { echo $string; } else { return $string; }
	}

/**
 * htmlFailure()
 */
function htmlBulletFailure($text='',$echo=false) {
	$string = '<span class="failure">' . $text . '</span><br />' . "\n";
	if ($echo) { echo $string; } else { return $string; }
	}

/**
 * htmlBulletHeading()
 */
function htmlBulletHeading($text='',$echo=false) {
	$string = '<p>' . $text . '</p>' . "\n";
	if ($echo) { echo $string; } else { return $string; }
	}

/**
 * htmlSuccess()
 */
function htmlBulletSuccess($text='',$echo=false) {
	$string = '<span class="success">' . $text . '</span><br />' . "\n";
	if ($echo) { echo $string; } else { return $string; }
	}

/**
 * Get the html charset
 * @return mixed
 * @see htmlCharsetSet()
 */
public function htmlCharsetGet() {
	return (is_string($this->htmlCharset)) ? $this->htmlCharset : false;
	}

/**
 * Set the html charset
 * @param string $txt
 * @return void
 * @see htmlCharsetGet()
 */
public function htmlCharsetSet($txt=null) {
	$this->htmlCharset = $txt;
	}

/**
 * Get the html doctype
 * @return mixed
 */
public function htmlDoctypeGet() {
	return (is_string($this->htmlDoctype)) ? $this->htmlDoctype : false;
	}

/**
 * Set the html doctype
 * @param string $txt
 * @return void
 * @see htmlDoctypeGet()
 */
public function htmlDoctypeSet($txt=null) {
	$this->htmlDoctype = $txt;
	}

/**
 * Get array of urls for js includes
 * @return array
 */
public function htmlJsGet() {
	return (is_array($this->htmlJs)) ? $this->htmlJs : array();
	}

/**
 * Add meta for header includes
 * @param string $txt
 * @return bool
 */
public function htmlMetaAdd($txt=null) {
	if (!is_array($this->htmlMeta)) { return false; }
	if (!is_string($txt) or strlen($txt) < 1) { return false; }
	if (in_array($txt,$this->htmlMeta)) { return true; }
	$this->htmlMeta[] = $txt;
	}

/**
 * Set array of meta for header includes
 * @return array
 */
public function htmlMetaGet() {
	return (is_array($this->htmlMeta)) ? $this->htmlMeta : array();
	}

/**
 * htmlSpacer()
 */
function htmlSpacer($echo=false) {
	$string = '&nbsp;&nbsp;';
	if ($echo) { echo $string; } else { return $string; }
	}

/**
 * htmlSuccessMsg()
 */
function htmlSuccessMsg($text='',$echo=false) {
	$string = '<p><span class="success">SUCCESS: ' . $text . '</span></p>'
	. "\n";
	if ($echo) { echo $string; } else { return $string; }
	}

/**
 * Is the specified variable name suitable?
 * @param string varName
 * @return bool
 */
private function isValidVarName($varName=null) {
	if (!is_string($varName)) { return false; }
	if (strlen($varName) < 1)   { return false; }
	if (strlen($varName) > 50)  { return false; }
	return true;
	}

/**
 * Set class properties based on _REQUEST/_POST variables
 * @return bool
 */
private function parseRequestVariables() {
	// Set actionRequest
	$str = $this->getRequestVar($this->qsAction);
	// actionRequest value must meet variable name requirements
	if (!$this->isValidVarName($str)) { return false; }
	$this->actionRequest = $str;
	return true;
	}

/**
 * Generate page to reload opening window and optionally close current window
 * @param bool $close
 * @param string $uri
 * @return void
 */
public function reloadOpener($close=true, $uri=null) {
	$js = array();
	if (empty($uri)) {
		$js[] = 'opener.location.reload()';
		} else {
		$js[] = "opener.location.href='" . trim(urldecode($uri)) . "'";
		}
	if ($close === true) {
		$js[] = 'window.close()';
		}
	$h = array('<html>');
	$h[] = '<body>';
	$h[] = '<script type="text/javascript">';
	$h[] = implode(";\n", $js);
	$h[] = '</script>';
	$h[] = '</body>';
	$h[] = '</html>';
	die(implode("\n", $h) . "\n");
	}

/**
 * Generate page to reload opening window and current window
 * @param string $self_uri
 * @param string $opener_uri
 * @return void
 */
public function reloadOpenerAndSelf($opener_uri=null, $self_uri=null) {
	$js = array();
	if (empty($opener_uri)) {
		$js[] = 'opener.location.reload()';
		} else {
		$js[] = "opener.location.href='" . trim(urldecode($opener_uri)) . "'";
		}
	if (empty($self_uri)) {
		$js[] = 'window.location.reload()';
		} else {
		$js[] = "window.location.href='" . trim(urldecode($self_uri)) . "'";
		}
	$h = array('<html>');
	$h[] = '<body>';
	$h[] = '<script type="text/javascript">';
	$h[] = implode(";\n", $js);
	$h[] = '</script>';
	$h[] = '</body>';
	$h[] = '</html>';
	die(implode("\n", $h) . "\n");
	}

/**
 * Generate page to reload current window
 * @param string $uri
 * @return void
 */
public function reloadSelf($uri=null) {
	$js = array();
	if (empty($uri)) {
		$js[] = 'window.location.reload()';
		} else {
		$js[] = "window.location.href='" . trim(urldecode($uri)) . "'";
		}
	$h = array('<html>');
	$h[] = '<body>';
	$h[] = '<script type="text/javascript">';
	$h[] = implode(";\n", $js);
	$h[] = '</script>';
	$h[] = '</body>';
	$h[] = '</html>';
	die(implode("\n", $h) . "\n");
	}

/**
 * Reset css to a blank slate
 * @return void
 * @see addCss(), getCss(), __construct()
 */
public function resetCss() { $this->css = array(); }

/**
 * Set breadcrumb trail
 * @return bool
 */
private function setBreadCrumbs() {
	if (!is_array($this->breadCrumbs)) { return false; }
	if (count($this->breadCrumbs) < 1) { return true;  }
	foreach($this->breadCrumbs as $crumb => $data) {
		$this->breadCrumbs[$crumb] = $this->getRequestVar($crumb);
		}
	return true;
	}

/**
 * Set document character set
 * @param string txt
 * @return void
 * @see getCharset(), __construct()
 */
public function setCharset($txt=null) { $this->charset = $txt; }

/**
 * Enable/disable viewing of context menu via right clicks
 * @param bool val
 * @return bool
 * @see getPageHeader()
 */
public function setContextMenu($val=null) {
	if (!is_bool($val)) { return false; }
	$this->contextMenu = $val;
	return true;
	}

/**
 * Set specified breadcrumb to specified value
 * @param string $crumb
 * @param string $data
 * @return bool
 */
public function setCrumb($crumb=null,$data=null) {
	if (!$this->crumbExists($crumb)) { return false; }
	$this->breadCrumbs[$crumb] = $data;
	return true;
	}

/**
 * Set debugging options
 * If flagged true, will turn on various debugging flags
 * @param bool val
 * @return bool
 */
public function setDebug($val=null) {
	if (!is_bool($val)) { return false; }
	$this->debug = $val;
	// turn on context menus if debugging is on
	if ($val === true) {
		$this->setContextMenu(true);
		}
	return true;
	}

/**
 * Set document type
 * @param string txt
 * @return void
 * @see getDoctype(), __construct()
 */
public function setDoctype($txt=null) { $this->doctype = $txt; }

/**
 * Set property names visible to html forms when listing objects.
 * @param array $props - array of property names
 * @return bool
 * @see addPropertyList(), getPropertiesList()
 */
public function setPropertiesList(&$props) {
	if (!is_array($props)) { return false; }
	$this->propertiesList = $props;
	return true;
	}

/**
 * Set the default search order property name
 */
public function setSearchOrderDefault($prop) {
	return $this->searchOrderDefault = $prop;
	}

/**
 * Set the default search property name
 */
public function setSearchPropertyDefault($prop) {
	return $this->searchPropertyDefault = $prop;
	}

/**
 * Set document template directory
 * @param string $dir
 * @return void
 */
public function setTemplateDir($dir=null) { $this->dirTmp = $dir; }

/**
 * Set document title
 * @param string txt
 * @return mixed
 * @see getTitle(), __construct()
 */
public function setTitle($txt=null) { $this->title = $txt; }

/**
 * Wrapper to set uri
 */
public function setUri($txt=null) { $this->uri = $txt; }

/**
 * Apply search terms specified by the user, if set.
 * @param $dbo - phpdbo object
 * @return bool
 */
public function setUserInput(&$dbo=null) {
	if (!($dbo instanceof phpdbo)) { return false; }
	$p = $this->getCrumb(WA_QS_PAGE);
	if (is_numeric($p) and $p > 0) {
		$this->pageCurrent = $p;
		} else {
		$this->pageCurrent = 1;
		}
	// order
	$o = $this->getCrumb(WA_QS_ORDER);
	// sort
	$s = ($this->getCrumb(WA_QS_SORT) == 'd') ? 'DESC' : 'ASC';
	if (is_numeric($o)) {
		if (is_array($this->propertiesList) and
		    array_key_exists($o,$this->propertiesList)) {
			$dbo->setSearchOrder($this->propertiesList[$o],$s);
			}
		}
	// Set sane limits
	if (!is_numeric($this->hitsMax) or $this->hitsMax < 1) {
		$this->hitsMax = WA_MAX_ROWS;
		}
	// searches
	if ($this->searches > 0) {
		for($j=0; $j < $this->searches; $j++) {
			$field = $this->getSearchField($j);
			$type  = $this->getSearchType($j);
			$term  = $this->getSearchTerm($j);
			if ($field == '' or $type == '' or $term == '') { continue; }
			if (!$dbo->isProperty($field)) { continue; }
			switch($type) {
				case 'contains':
					$term = '%' . $term . '%';
					$dbo->setSearchFilter($field,$term,'like');
				break;
				case 'equals':
					$dbo->setSearchFilter($field,$term);
				break;
				case 'begins with':
					$term = $term . '%';
					$dbo->setSearchFilter($field,$term,'like');
				break;
				case 'ends with':
					$term = $term . '%';
					$dbo->setSearchFilter($field,$term,'like');
				break;
				case 'less than':
					$dbo->setSearchFilter($field,$term,'<');
				break;
				case 'greater than':
					$dbo->setSearchFilter($field,$term,'>');
				break;
				case 'does not equal':
					$dbo->setSearchFilter($field,$term,'!=');
				break;
				case 'does not contain':
					$term = '%' . $term . '%';
					$dbo->setSearchFilter($field,$term,'not like');
				break;
				case 'does not begin with':
					$term = $term . '%';
					$dbo->setSearchFilter($field,$term,'not like');
				break;
				case 'does not end with':
					$term = '%' . $term;
					$dbo->setSearchFilter($field,$term,'not like');
				break;
				default:
					continue;
				break;
				}
			}
		}
	return true;
	}

/**
 * Set specified template variable
 * @return bool
 * @see getVar(), varIsSet(), showVar()
 */
public function setVar($varname = null, &$data = false) {
	if (!is_array($this->templateVars))   { return false; }
	if (!$this->isValidVarName($varname)) { return false; }
	$this->templateVars[$varname] = $data;
	return true;
	}

/**
 * Echo specified template var to the screen
 * @param string $varname
 * @return void
 */
public function showVar($varname) { echo $this->getVar($varname); }

/**
 * Convert comma, line, and/or space separated text into an array
 * @param string $text
 * @return mixed
 */
public function textAreaToArray($text='') {
	if (is_array($text) and count($text) > 0) { return $text; }
	if (!is_string(trim($text))) { return false; }
	if (trim($text) == '')       { return false; }
	// convert spaces and commas
	$text   = str_replace(' ', "\n", $text);
	$text   = str_replace(',', "\n", $text);
	$text   = str_replace("\r\n", "\n", $text);
	$text   = explode("\n", $text);
	if (!is_array($text) or count($text) < 1) { return false; }
	// don't include duplicates
	$ret_ar = array();
	foreach($text as $value) {
		$value = trim(strtolower($value));
		if (!is_string($value) or strlen($value) < 1) { continue; }
		if (!in_array($value,$ret_ar)) {
			$ret_ar[] = $value;
			}
		}
	if (!is_array($ret_ar) or count($ret_ar) < 1) {
		return false;
		}
	return (count($ret_ar) > 0) ? $ret_ar : false;
	}

/**
 * Convert line, and/or space separated comma-delimited text into an array
 * @param string $text
 * @return mixed
 */
public function textAreaToCsvArray($text='') {
	if (is_array($text) and count($text) > 0) { return $text; }
	if (!is_string(trim($text))) { return false; }
	if (trim($text) == '')       { return false; }
	// convert spaces and line feeds
	$text   = str_replace("\r\n", "\n", $text);
	$text   = str_replace(' ', "\n", $text);
	$text   = explode("\n", $text);
	if (!is_array($text) or count($text) < 1) { return false; }
	// don't include duplicates
	$ret_ar = array();
	foreach($text as $value) {
		$value = trim(strtolower($value));
		if (!is_string($value) or strlen($value) < 1) { continue; }
		$junk = explode(',', $value);
		if (count($junk) !== 2) { continue; }
		$key = trim($junk[0]);
		$val = trim($junk[1]);
		if ($key == '' or $val == '') { continue; }
		if (array_key_exists($key,$ret_ar)) {
			continue;
			} else {
			$ret_ar[$key] = $val;
			}
		}
	if (!is_array($ret_ar) or count($ret_ar) < 1) {
		return false;
		}
	return $ret_ar;
	}

/**
 * Send content to browser as a file upload
 * @param string $content
 * @param string $filename
 * @return void
 */
public function uploadContent(&$content=null, $filename='upload.txt') {
	header('Pragma: public');
	header('Expires: 0');
	header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
	header('Content-Type: application/octet-stream');
	header('Content-Length: ' . strlen($content));
	header('Content-Disposition: attachment; filename="' . $filename . '"');
	header('Content-Transfer-Encoding: binary' . "\n");
	die($content);
	}

/**
 * Is specified template var name set and not empty?
 * @param string $varname
 * @return bool
 */
public function varIsSet($varname=null) {
	$data = $this->getVar($varname);
	if ($data === false) { return false; }
	if (is_string($data) and strlen($data) > 0) { return true; }
	if (is_array($data) and count($data) > 0) { return true; }
	if (is_object($data)) { return true; }
	return false;
	}
}

/**
 * Generic popup generating class
 */
class popup {

/**
 * Link properties
 */
private $linkClass = null;
private $linkText  = 'popup link';
private $linkUri   = 'about:blank';

/**
 * Window properties
 */
private $height      = 300;
private $locationbar = false;
private $menubar     = false;
private $resize      = true;
private $scrollbars  = false;
private $toolbar     = false;
private $width       = 400;
private $windowName  = 'popup';

/**
 * Generate popup window link
 * @param string $uri
 * @param string $txt
 * @param string $wn (window name)
 * @return string
 */
public function get($uri=null,$txt=null,$wn=null) {
	if (!is_null($uri)) { $this->linkUri    = $uri; }
	if (!is_null($txt)) { $this->linkText   = $txt; }
	if (!is_null($wn))  { $this->windowName = $wn;  }
	$hr = '<a ';
	if (!is_null($this->linkClass)) {
		$hr .= 'class="' . $this->linkClass . '" ';
		}
	$hr .= 'href="javascript:void(0);" onclick="javascript:'
	. 'nw=window.open(\'' . $this->linkUri . '\',\'' . $this->windowName
	. '\',\'' . $this->getWindowOptions() . '\');nw.opener=self;return '
	. 'false;">' . $this->linkText . '</A>';
	return $hr;
	}

/**
 * Set window width (x)
 * @param int x
 * @return void
 */
public function setX($x=null) {
	if (is_numeric($x) and $x > 0) { $this->width = $x; }
	}

/**
 * Set window height (y)
 * @param int y
 * @return void
 */
public function setY($y=null) {
	if (is_numeric($y) and $y > 0) { $this->height = $y; }
	}

/**
 * Generate window options string for javascript window.open statement
 * @return string
 */
private function getWindowOptions() {
	$ar = array();
	if (is_numeric($this->width)) {
		$ar[] = 'width=' . $this->width;
		}
	if (is_numeric($this->height)) {
		$ar[] = 'height=' . $this->height;
		}
	$ar[] = 'location='   . (($this->locationbar === true) ? 'yes' : 'no');
	$ar[] = 'menubar='    . (($this->menubar === true)     ? 'yes' : 'no');
	$ar[] = 'resizable='  . (($this->resize === true)      ? 'yes' : 'no');
	$ar[] = 'scrollbars=' . (($this->scrollbars === true)  ? 'yes' : 'no');
	$ar[] = 'toolbar='    . (($this->toolbar === true)     ? 'yes' : 'no');
	return implode(',',$ar);
	}
}
?>
